﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
using Tempest.Messages;

namespace TempestChessComputer
{
    class Game
    {
        TempestComputerPlayer client;
        Board board;
        GameType gameType;
        int playerPosition;
        int teamConfiguration;
        Stopwatch stopwatch;        
        long startTime;
        int moveSpeed;
        int delayDuration;
        Queue<PieceCaptured> captures;
        Queue<PieceMoved> moves;
        Queue<PiecePlaced> places;
        Queue<PieceInDanger> dangers;        
        Random r = new Random();
        bool isCrazy;
        bool isRunning;

        public Game(TempestComputerPlayer tempestComp, GameType gameType, int playerPosition, int teamConfig, int moveSpeed, int delayDuration)
        {
            client = tempestComp;
            this.gameType = gameType;
            this.playerPosition = playerPosition;
            teamConfiguration = teamConfig;
            this.moveSpeed = moveSpeed;
            this.delayDuration = delayDuration;

            stopwatch = new Stopwatch();
            isRunning = false;

            captures = new Queue<PieceCaptured>();
            moves = new Queue<PieceMoved>();
            places = new Queue<PiecePlaced>();
            dangers = new Queue<PieceInDanger>();            

            isCrazy = gameType == GameType.CRAZYHOUSE || gameType == GameType.FOURWAYCRAZYHOUSE || gameType == GameType.BUGHOUSE;         
            board = new Board(gameType, moveSpeed, delayDuration);        
        }

        private void makeMoves(long time)
        {
            foreach (Piece p in board.PiecesById.Values)
            {
                if (p.Player == playerPosition && !p.IsMoving && !p.IsDelayed)
                {                    
                    Move bestMove = evaluateMoves(p, time);
                    
                    if (bestMove != null)
                    {                        
                        makeMove(p.PieceId, bestMove.TargetX, bestMove.TargetY);
                    }
                }
            }

            if(isCrazy)
            {
                foreach (Piece p in board.PiecesInHand)
                {
                    if (p.Player == playerPosition)
                    {
                        dropPiece(p.Type, r.Next(board.BoardWidth), r.Next(board.BoardWidth));
                    }
                }
            }
        }

        //evaluates each possible move for a piece and returns the best one
        private Move evaluateMoves(Piece p, long time)
        {
            Move bestMove = new Move(p.PieceId, 0, 0, -10000);
            List<Move> moves = p.getMoves();
            foreach (Move m in moves)
            {

                if (m.TargetX == p.X && m.TargetY == p.Y)
                {
                    //not moving: if under attack, not moving loses this piece. Otherwise, value of not moving is 0
                    m.Value = p.isUnderAttack(time) ? 0 - p.PieceValue : 0;                    
                }
                else if (m.IsEnpassant)
                {
                    //en passant captures a pawn, so value = value of the pawn captured
                    m.Value = p.PieceValue;
                }
                else
                {
                    Piece target = board.getPieceAtOrMovingTo(m.TargetX, m.TargetY);
                    if (target != null && target.Player != p.Player)
                    {
                        //a capture--value is value of captured piece - value of this piece + 1
                        m.Value = target.PieceValue - p.PieceValue + 1;
                    }
                    else
                    {
                        //not a capture
                        //room for improvement here!
                        m.Value = r.NextDouble();
                    }
                }

                //update best move found so far
                if (m.Value > bestMove.Value)
                {                    
                    bestMove = m;
                }
            }

            return bestMove;
        }

        public void update()
        {                     
            processQueuedMessages();
            board.update(stopwatch.ElapsedMilliseconds);
            makeMoves(stopwatch.ElapsedMilliseconds);                    
        }

        private void processQueuedMessages()
        {
            Dictionary<int, Piece> piecesById = board.PiecesById;
            lock (moves)
            {
                while (moves.Count > 0)
                {
                    PieceMoved move = moves.Dequeue();
                    if (!piecesById.ContainsKey(move.PieceId))
                        continue;
                    Piece p = piecesById[move.PieceId];
                    p.TargetX = move.TargetX;
                    p.TargetY = move.TargetY;
                    p.IsMoving = true;
                    p.MoveStartTime = move.MoveStartingTime;
                    p.MoveDelayStart = move.MoveDelayStart;                    
                    p.AttackerPieceId = -1;
                    p.MoveDelayDuration = delayDuration;
                }
            }

            lock (places)
            {
                while (places.Count > 0)
                {
                    PiecePlaced place = places.Dequeue();
                    Piece p = new Piece((PieceType)place.Type, board, place.PieceId, place.Player);                    
                    p.X = place.X;
                    p.Y = place.Y;
                    p.MoveDelayStart = place.PlaceTime;
                    p.IsDelayed = true;
                    p.MoveDelayDuration = (3 * delayDuration) / 2;
                    board.PiecesInHand.Remove(p);
                    piecesById.Add(p.PieceId, p);
                }
            }

            lock (dangers)
            {
                while (dangers.Count > 0)
                {
                    PieceInDanger danger = dangers.Dequeue();
                    if (!piecesById.ContainsKey(danger.PieceId))
                        continue;
                    Piece p = piecesById[danger.PieceId];
                    p.AttackerPieceId = danger.AttackerPieceId;
                    p.CaptureTime = danger.CaptureTime;
                }
            }           

            lock (captures)
            {
                while (captures.Count > 0)
                {
                    PieceCaptured capture = captures.Dequeue();
                    if (!piecesById.ContainsKey(capture.PieceId))
                        continue;

                    if (isCrazy)
                    {
                        Piece capturedPiece = piecesById[capture.PieceId];
                        Piece capturingPiece = piecesById[capture.AttackerPieceId];
                        capturedPiece.Player = capturingPiece.Player;
                        board.PiecesInHand.Add(capturedPiece);
                    }

                    piecesById.Remove(capture.PieceId);
                    
                }
            }
        }       

        public void startGame(long startTime)
        {
            this.startTime = startTime;
            stopwatch.Reset();
            stopwatch.Start();
            isRunning = true;
            new Thread(runGame).Start();
        }

        private void runGame()
        {
            while (isRunning)
            {
                update();
                Thread.Sleep(50);
            }
        }

        public void stopGame()
        {
            stopwatch.Stop();
            isRunning = false;
        }

        public void pieceCaptured(PieceCaptured capture)
        {            
            lock (captures)
            {
                captures.Enqueue(capture);
            }
        }

        public void pieceInDanger(PieceInDanger danger)
        {
            lock (danger)
            {
                dangers.Enqueue(danger);
            }            
        }

        public void piecePlaced(PiecePlaced place)
        {            
            lock (places)
            {
                places.Enqueue(place);
            }
        }

        public void pieceMoved(PieceMoved move)
        {
            lock (moves)
            {
                moves.Enqueue(move);
            }
        }            

        private void makeMove(int pieceId, int x, int y)
        {
            client.SendPieceMove(pieceId, x, y);
        }

        private void dropPiece(PieceType type, int x, int y)
        {
            client.DropPiece(type, x, y);
        }

        private void offerDraw()
        {
            client.SendOfferDraw();
        }
    }
}
